/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package com.wider.foundation.event;

import com.wider.foundation.concurrency.AggressiveWorker;
import com.wider.foundation.concurrency.CountedBlockingQueue;
import com.wider.foundation.concurrency.Poison;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * This class provides abstraction of a Hub for binding events to user actions.
 * This implementation of EventTarget DOES NOT limit to any event type.
 *
 * @author William <avextk@gmail.com>
 */
public class EventHub implements EventTarget {
    private HashMap                 eventmap;
    private CountedBlockingQueue    eventque;
    private AggressiveWorker        repeater;
    private boolean                 pwrstate;
    public static boolean           STATE_ON = true;
    public static boolean           STATE_OFF = false;
    public static int               MAX_EVENT_QUEUE_LEN = 32;

    public EventHub(){
        pwrstate = false;
        eventmap = new HashMap<String, ArrayList<EventListener>>();
        eventque = new CountedBlockingQueue<Runnable>(MAX_EVENT_QUEUE_LEN);
        repeater = new AggressiveWorker("Event Delegation");
        repeater.setSource(eventque);
    }

    public void bootup(){
        if ((STATE_OFF == pwrstate) && (!eventmap.isEmpty())){
            repeater.start();
            pwrstate = STATE_ON;
        }
    }

    public void shutdown() throws InterruptedException{
        pwrstate = false;
        repeater.stop();
        eventque.offer(new Poison());
        repeater.join();
    }

    @Override
    public void fire(Event event) {
        ArrayList<EventListener> listeners;

        if (null == event)
            throw new IllegalArgumentException("Instance of Event MUST BE specified.");

        listeners = (ArrayList<EventListener>) eventmap.get(event.getEventID());
        
        if (null == listeners)
            return;

        EventDelegation delegate = new EventDelegation(event, listeners);
        eventque.offer(delegate);
        
    }

    @Override
    public void addEventListener(String event_id, EventListener listener) {
        ArrayList<EventListener> eventlisteners;

        if (null == event_id)
            throw new IllegalArgumentException("Event ID MUST be specified.");

        if (null == listener)
            throw new IllegalArgumentException("Listener MUST NOT be null.");
        
        if (null == (eventlisteners = (ArrayList<EventListener>) eventmap.get(event_id))) {
            eventlisteners = new ArrayList<EventListener>();
            eventmap.put(event_id, eventlisteners);
        }

        if (false == eventlisteners.contains(listener)){
            eventlisteners.add(listener);
            bootup();
        }
    }

    @Override
    public void removeEventListener(String event_id, EventListener listener) {
        ArrayList<EventListener> eventlisteners;
        
        if (null == event_id)
            throw new IllegalArgumentException("Event ID MUST be specified.");

        if (null == listener)
            throw new IllegalArgumentException("Listener MUST NOT be null.");

        if (null != (eventlisteners = (ArrayList<EventListener>) eventmap.get(event_id))) {
            eventlisteners.remove(listener);
        }
    }

    @Override
    public Set getListeningEvents(String... EventIDs) {
        Set<String> events;

        events = eventmap.keySet();

        if (EventIDs.length > 0){
            Set<String> event_ids = new HashSet<String>(Arrays.asList(EventIDs));
            event_ids.retainAll(events);
            events = event_ids;
        }

        return events;
    }

    @Override
    public Set getRegardedEvents(String... keywords) {
        return getListeningEvents(keywords);
    }

    /**
     * Private Embedded Classes
     */

    private class EventDelegation implements Runnable {
        private Event       event;
        private ArrayList<EventListener>        lsnrs;

        public EventDelegation(Event event, ArrayList<EventListener> listeners){
            this.event = event;
            this.lsnrs = listeners;
        }

        @Override
        public void run() {
            for (EventListener listener : lsnrs){
                listener.handleEvent(event);
            }
        }
        
    }
}
